/*
 * Copyright (C) 2018 Hisilicon Limited.
 *
 * This program is free software; you can redistribute it and /or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version
 */

#include "devdrv_atu.h"
#include "nvme_drv.h"
#include "apb_drv.h"
#include "devdrv_dma.h"

extern int devdrv_get_atu_info(u32 devid, int atu_type, struct devdrv_iob_atu **atu, u64 *host_phy_base);

/*      this addr should be visited by ts, ts online has 40 bit address space, so we only use 64bit space
        dev2 tx atu config:
        mem space 256GB: dev0 32GB, dev1 32GB, io reg/host space, dev3 32GB, ... , dev7 32GB
        io space/host 32GB:   io space dev0 4GB,  io space dev1 4GB, host 4GB, io space dev3 4GB,
                              ...  io space dev7 4GB
*/
int devdrv_set_tx_atu(void __iomem *apb_base, u32 devid, u32 dst_devid, u32 atu_type, u64 target_addr, u64 target_size,
    struct devdrv_shr_para __iomem *para)
{
    u32 atu_id;
    u64 base_size;
    u64 base_addr;
    struct devdrv_iob_atu atu;

    if (DEVDRV_P2P_SURPORT_MAX_DEVICE == 0) {
        devdrv_err("DEVDRV_P2P_SURPORT_MAX_DEVICE:%d\n", DEVDRV_P2P_SURPORT_MAX_DEVICE);
        return -EINVAL;
    }

    if (atu_type == ATU_TYPE_TX_MEM) {
        base_size = para->tx_atu_base_size1 / DEVDRV_P2P_SURPORT_MAX_DEVICE;
        base_addr = para->tx_atu_base_addr1 + base_size * dst_devid;
        atu_id = dst_devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM;
    } else {
        base_size = para->tx_atu_base_size1 / DEVDRV_P2P_SURPORT_MAX_DEVICE;
        base_addr = para->tx_atu_base_addr1 + base_size * devid;
        base_size = base_size / DEVDRV_P2P_SURPORT_MAX_DEVICE;
        if (atu_type == ATU_TYPE_TX_IO) {
            base_addr = base_addr + base_size * dst_devid;
            atu_id = dst_devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM + 1;
        } else {
            base_addr = base_addr + base_size * devid;
            atu_id = devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM;
        }
    }

    if (target_size > base_size) {
        devdrv_err("dev %d tx atu config type %d size 0x%llx is too large 0x%llx\n", devid, atu_type, target_size,
            base_size);
        return -EINVAL;
    }

    atu.target_addr = target_addr;
    atu.base_addr = base_addr;
    atu.size = target_size;

    devdrv_info("dev %d dst dev %d tx atu config type %d atu_id %d size 0x%llx\n", devid, dst_devid, atu_type, atu_id,
        target_size);

    devdrv_add_tx_atu(apb_base, atu_id, DEVDRV_PF_NUM, &atu);
    return 0;
}

int devdrv_tx_atu_init(const void __iomem *apb_base, struct devdrv_iob_atu atu[], int num)
{
    int i;

    for (i = 0; i < num; i++) {
        if (0 == devdrv_get_tx_atu(apb_base, i, DEVDRV_PF_NUM, &atu[i]))
            atu[i].valid = ATU_VALID;
        else
            atu[i].valid = ATU_INVALID;
    }

    return 0;
}

int devdrv_get_dev_tx_atu(const void __iomem *apb_base, struct devdrv_iob_atu atu[], u32 len, u32 devid, u32 dst_devid)
{
    u32 atu_id, i, atu_num;

    if (dst_devid == (u32)(-1)) { /* tx atu to host */
        atu_id = devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM;
        atu_num = 1;
    } else {
        atu_id = dst_devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM;
        atu_num = DEVDRV_SINGLE_DEV_TX_ATU_NUM;
    }

    for (i = 0; i < atu_num && atu_id < len; i++, atu_id++) {
        if (0 == devdrv_get_tx_atu(apb_base, atu_id, DEVDRV_PF_NUM, &atu[atu_id]))
            atu[atu_id].valid = ATU_VALID;
        else
            atu[atu_id].valid = ATU_INVALID;
    }

    return 0;
}

int devdrv_del_dev_tx_atu(void __iomem *apb_base, struct devdrv_iob_atu atu[], u32 len, u32 devid, u32 dst_devid)
{
    u32 atu_id, i, atu_num;

    if (dst_devid == (u32)(-1)) { /* tx atu to host */
        atu_id = devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM;
        atu_num = 1;
    } else {
        atu_id = dst_devid * DEVDRV_SINGLE_DEV_TX_ATU_NUM;
        atu_num = DEVDRV_SINGLE_DEV_TX_ATU_NUM;
    }

    for (i = 0; i < atu_num && atu_id < len; i++, atu_id++) {
        devdrv_del_tx_atu(apb_base, atu_id, DEVDRV_PF_NUM, &atu[atu_id]);
        atu[atu_id].valid = ATU_INVALID;
    }

    return 0;
}

int devdrv_mem_rx_atu_init(const void __iomem *apb_base, struct devdrv_iob_atu atu[], int num)
{
    int ret;
    ret = devdrv_rx_atu_init(apb_base, DEVDRV_PF_NUM, PCI_BAR_MEM, atu, num);
    return ret;
}

void devdrv_mem_rx_atu_show(const void __iomem *apb_base)
{
    struct devdrv_iob_atu mem_rx_atu[DEVDRV_MAX_RX_ATU_NUM];
    (void)devdrv_rx_atu_init(apb_base, DEVDRV_PF_NUM, PCI_BAR_MEM, mem_rx_atu, DEVDRV_MAX_RX_ATU_NUM);
}

void devdrv_io_rx_atu_show(const void __iomem *apb_base)
{
    struct devdrv_iob_atu io_rx_atu[DEVDRV_MAX_RX_ATU_NUM];
    (void)devdrv_rx_atu_init(apb_base, DEVDRV_PF_NUM, PCI_BAR_IO, io_rx_atu, DEVDRV_MAX_RX_ATU_NUM);
}

void devdrv_rsv_mem_rx_atu_show(const void __iomem *apb_base)
{
    struct devdrv_iob_atu rsv_mem_rx_atu[DEVDRV_MAX_RX_ATU_NUM];
    (void)devdrv_rx_atu_init(apb_base, DEVDRV_PF_NUM, PCI_BAR_RSV_MEM, rsv_mem_rx_atu, DEVDRV_MAX_RX_ATU_NUM);
}

void devdrv_rx_atu_show(const void __iomem *apb_base)
{
    devdrv_mem_rx_atu_show(apb_base);
    devdrv_io_rx_atu_show(apb_base);
    devdrv_rsv_mem_rx_atu_show(apb_base);
}

int devdrv_atu_base_to_target(struct devdrv_iob_atu atu[], int num, u64 base_addr, u64 *target_addr)
{
    int i;

    for (i = 0; i < num; i++) {
        if (atu[i].valid == ATU_INVALID)
            continue;

        if ((atu[i].base_addr <= base_addr) && ((atu[i].base_addr + atu[i].size) > base_addr)) {
            *target_addr = base_addr - atu[i].base_addr + atu[i].target_addr;
            return 0;
        }
    }

    return -1;
}

int devdrv_atu_target_to_base(struct devdrv_iob_atu atu[], int num, u64 target_addr, u64 *base_addr)
{
    int i;

    for (i = 0; i < num; i++) {
        if (atu[i].valid == ATU_INVALID)
            continue;

        if ((atu[i].target_addr <= target_addr) && ((atu[i].target_addr + atu[i].size) > target_addr)) {
            *base_addr = target_addr - atu[i].target_addr + atu[i].base_addr;
            return 0;
        }
    }

    return -1;
}

int devdrv_devmem_addr_h2d(u32 devid, phys_addr_t host_bar_addr, phys_addr_t *device_phy_addr)
{
    struct devdrv_iob_atu *atu = NULL;
    u64 host_phy_base = 0;

    if (device_phy_addr == NULL) {
        devdrv_err("dev %d device_phy_addr is null\n", devid);
        return -EINVAL;
    }

    if (devdrv_get_atu_info(devid, ATU_TYPE_RX_MEM, &atu, &host_phy_base)) {
        devdrv_err("dev %d find atu failed.\n", devid);
        return -EINVAL;
    }

    if (host_bar_addr < host_phy_base) {
        devdrv_err("dev %d host_bar_addr is small than host_phy_base\n", devid);
        return -EINVAL;
    }

    if (devdrv_atu_base_to_target(atu, DEVDRV_MAX_RX_ATU_NUM, (u64)(host_bar_addr - host_phy_base),
        (u64 *)device_phy_addr)) {
        devdrv_err("dev %d host_bar_addr not found.\n", devid);
        return -EINVAL;
    }

    return 0;
}
EXPORT_SYMBOL(devdrv_devmem_addr_h2d);

int devdrv_devmem_addr_d2h(u32 devid, phys_addr_t device_phy_addr, phys_addr_t *host_bar_addr)
{
    struct devdrv_iob_atu *atu = NULL;
    u64 host_phy_base = 0;

    if (host_bar_addr == NULL) {
        devdrv_err("dev %d host_bar_addr is null\n", devid);
        return -EINVAL;
    }

    if (devdrv_get_atu_info(devid, ATU_TYPE_RX_MEM, &atu, &host_phy_base)) {
        devdrv_err("dev %d find atu failed.\n", devid);
        return -EINVAL;
    }

    if (devdrv_atu_target_to_base(atu, DEVDRV_MAX_RX_ATU_NUM, (u64)device_phy_addr, (u64 *)host_bar_addr)) {
        devdrv_err("dev %d device_phy_addr not found.\n", devid);
        return -EINVAL;
    }

    *host_bar_addr += host_phy_base;

    return 0;
}
EXPORT_SYMBOL(devdrv_devmem_addr_d2h);
